package com.black.service.service;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.black.protocol.ErrorCode;
import com.black.protocol.Response;
import com.black.service.dao.UserDao;
import com.black.service.entity.UserEntity;
import com.black.service.util.RedisUtil;
import com.black.utils.encrypt.sm4.SM4Util;
import com.black.utils.file.FileTypeEnum;
import com.black.utils.file.FileUtil;
import com.black.utils.file.image.code.VerifyCodeUtil;
import com.black.utils.mail.MailProperties;
import com.black.utils.mail.MailSender;
import com.black.utils.network.app.http.CookieUtil;
import com.black.utils.random.RandomUtil;
import com.black.utils.token.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.function.Supplier;

@Slf4j
@Service
public class UserServiceImpl implements UserService {
    @Value("${verify.size}")
    private int size;

    @Value("${tmp.file.location}")
    private String path;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private UserDao userDao;

    private static final String CODE = "code";

    @Override
    public Response<?> verifyCode(HttpServletRequest request) {
        try {
            log.info("获取验证码");
            String subPath = FileUtil.buildPath(path, CODE);
            File codeImg = new File(subPath + RandomUtil.generateUUID() + FileTypeEnum.PNG.getSuffix());
            String verifyCode = VerifyCodeUtil.outputVerifyImage(200, 50, codeImg, size);
            redisUtil.setWithExpire(CookieUtil.getCookie(request), verifyCode, 150);
            return Response.success(FileUtil.encodeBase64(codeImg));
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        return null;
    }

    @Override
    public Response<?> emailCode(HttpServletRequest request, JSONObject param) {
        String receiver = param.getString("email");
        int operation = (int) param.getOrDefault("operation", -1);
        String verifyCode = VerifyCodeUtil.generateVerifyCode(size);
        String subject;
        String msg;
        switch (operation) {
            case 1:
                subject = "注册账号";
                msg = "欢迎注册小趴菜";
                break;
            case 2:
                subject = "修改密码";
                msg = "您正在修改密码";
                break;
            case 3:
                subject = "修改邮箱";
                msg = "您正在修改绑定邮箱";
                break;
            case 4:
                subject = "忘记密码";
                msg = "您正在重置密码";
                break;
            default:
                subject = "告警";
                msg = "非法操作";
                receiver = "739272187@qq.com";
        }
        MailSender.init(MailProperties.Builder.build(MailProperties.QQ));
        MailSender.send(subject, msg + ",您的验证码是%s验证码将在2分30秒后失效,请尽快完成操作,若非本人操作,请忽略", receiver, verifyCode);
        redisUtil.setWithExpire(CookieUtil.getCookie(request), verifyCode, 150);
        return Response.success(true);
    }

    @Override
    public Response<?> register(HttpServletRequest request, JSONObject param) {
        return checkCodeAndThen(request, param, () -> {
            String username = param.getString("username");
            String password = SM4Util.encryptData_CBC(param.getString("password"));
            String email = param.getString("email");
            Long count = userDao.selectCount(Wrappers.<UserEntity>lambdaQuery().eq(UserEntity::getUsername, username));
            if (count!=null && count > 0) {
                return Response.fail(ErrorCode.ERROR_USER_ALREADY_EXIST);
            }
            UserEntity entity = new UserEntity();
            entity.setUsername(username);
            entity.setPassword(password);
            entity.setEmail(email);
            int result = userDao.insert(entity);
            if (result > 0) {
                String token = TokenUtil.makeToken(username);
                redisUtil.setWithExpire(token, JSONObject.toJSONString(entity), 1800);
                return Response.success(token);
            }
            return Response.fail();
        });
    }

    @Override
    public Response<?> login(HttpServletRequest request, JSONObject param) {
        return checkCodeAndThen(request, param, () -> {
            String username = param.getString("username");
            UserEntity entity = userDao.selectOne(Wrappers.<UserEntity>lambdaQuery().eq(UserEntity::getUsername, username).last("limit 1"));
            if (entity==null) {
                return Response.fail(ErrorCode.ERROR_USER_OR_PASS);
            }
            String password = param.getString("password");
            if (!SM4Util.encryptData_CBC(password).equals(entity.getPassword())) {
                int errorTimes = entity.getErrorTimes() + 1;
                entity.setErrorTimes(errorTimes);
                if (errorTimes >= 5) {
                    entity.setLock(true);
                }
                userDao.updateById(entity);
                if (entity.isLock()) {
                    return Response.fail(ErrorCode.ERROR_USER_LOCK);
                }
                return Response.fail(ErrorCode.ERROR_USER_OR_PASS);
            }
            String token = TokenUtil.makeToken(username);
            redisUtil.setWithExpire(token, JSONObject.toJSONString(entity), 1800);
            entity.setLastLoginTime(null);
            entity.setErrorTimes(0);
            userDao.updateById(entity);
            return Response.success(token);
        });
    }

    @Override
    public Response<?> logout(JSONObject param) {
        String token = param.getString("token");
        if (token==null || token.isEmpty()) {
            return Response.fail(ErrorCode.ERROR_PARAM_MISSING);
        }
        if (redisUtil.delete(token)) {
            return Response.success(true);
        }
        return Response.fail(ErrorCode.ERROR_TOKEN_INVALID);
    }

    @Override
    public Response<?> checkEmail(JSONObject param) {
        UserEntity entity = userDao.selectOne(Wrappers.<UserEntity>lambdaQuery().eq(UserEntity::getEmail, param.getString("email")).last("limit 1"));
        if (entity==null) {
            return Response.fail(ErrorCode.ERROR_EMAIL_NOT_EXIST);
        }
        return Response.success(true);
    }

    @Override
    public Response<?> forget(HttpServletRequest request, JSONObject param) {
        return checkCodeAndThen(request, param, () -> {
            UserEntity entity = userDao.selectOne(Wrappers.<UserEntity>lambdaQuery().eq(UserEntity::getEmail, param.getString("email")).last("limit 1"));
            entity.setPassword(SM4Util.encryptData_CBC(param.getString("password")));
            userDao.updateById(entity);
            return Response.success(true);
        });
    }

    @Override
    public Response<?> unsubscribe(HttpServletRequest request, JSONObject param) {
        return null;
    }

    @Override
    public Response<?> update(HttpServletRequest request, JSONObject param) {
        return null;
    }

    @Override
    public Response<?> donatePoints(HttpServletRequest request, JSONObject param) {
        return null;
    }

    private Response<?> checkCodeAndThen(HttpServletRequest request, JSONObject param, Supplier<Response<?>> supplier) {
        String verifyCode = redisUtil.get(CookieUtil.getCookie(request));
        if (verifyCode==null || verifyCode.isEmpty()) {
            return Response.fail(ErrorCode.ERROR_CODE_TIME_OUT);
        }
        if (!verifyCode.equalsIgnoreCase(param.getString("code"))) {
            return Response.fail(ErrorCode.ERROR_CODE_WRONG);
        }
        return supplier.get();
    }
}
